package se.webbzon.boltzmann.inventory;

import java.awt.Point;
import java.util.ArrayList;
import java.util.Collection;

import se.webbzon.boltzmann.debug.Debug;
import se.webbzon.boltzmann.item.AbstractItem;

public class Inventory {
	
	// The number of items per row
	private static final int itemsPerRow = 6;
	
	// A inventory size enumeration type
	public static enum Size {SMALL, MEDIUM, BIG, HUGE};
	
	// A collection of all the listeners to this inventory
	private final Collection<InventoryListener> listeners;
	
	// The size of the inventory
	private Size size;
	
	// The number of rows in the inventory
	private int rows;
	
	// The number of columns in the inventory
	private int columns;
	
	// The content of the inventory
	private AbstractItem[] content;
	
	// The number of items in this inventory
	private int items;
	
	/*============================================================
	Constructors
	============================================================*/
	
	/** Creates a new inventory. **/
	public Inventory(Size inventorySize) {
		this(inventorySize,null);
	}
	
	/** Creates a new inventory. **/
	public Inventory(Size inventorySize, AbstractItem[] items) {
		listeners = new ArrayList<InventoryListener>();
		this.content = items;
		this.size = inventorySize;
		this.items = 0;
		updateInventory(size);
	}
	
	/*============================================================
	Public Methods
	============================================================*/
	
	/** Returns the number of rows of an inventory with the provided size. **/
	public static int rowsOf(Size size) {
		switch (size) {
		case SMALL: return 1;
		case MEDIUM: return 2;
		case BIG: return 3;
		case HUGE: return 4;
		default: return 1;
		}
	}
	
	/** Returns the number of the columns of an inventory with the 
	 * provided size. **/
	public static int columnsOf(Size size) {
		return itemsPerRow;
	}
	
	/** Adds a listener to this inventory. **/
	public boolean addListener(InventoryListener listener) {
		int d;
		Debug.debugCollection(this, listeners);
		return listeners.add(listener);
	}
	
	/** Removes a listener from this inventory. **/
	public boolean removeListener(InventoryListener listener) {
		return listeners.remove(listener);
	}
	
	/** Clears all the listeners from this inventory. **/
	public void clearListeners() {
		listeners.clear();
	}
	
	/** Returns the size of the inventory. **/
	public Size getInventorySize() {
		return size;
	}
	
	/** Sets the size of the inventory. **/
	public void setInventorySize(Size inventorySize) {
		size = inventorySize;
		updateInventory(size);
		notifyInventoryResized(size);
	}
	
	/** Returns the number of rows in the inventory. **/
	public int getRows() {
		return rows;
	}
	
	/** Returns the number of columns in the inventory. **/
	public int getColumns() {
		return columns;
	}
	
	/** Returns the maximal number of items which can be stored in the inventory. **/
	public int getMaxItems() {
		return content.length;
	}
	
	/** Returns the number of items stored in the inventory. **/
	public int getNumberOfItems() {
		return items;
	}
	
	/** Returns true if the given item was added to the inventory. 
	 * The item will be added to the first empty position in the inventory. **/
	public boolean addItem(AbstractItem item) {
		return addItem(item,0,0);
	}
	
	/** Returns true if the given item was added to the inventory. 
	 * The item will be added to the first empty position after or at the 
	 * provided column and row. **/
	public boolean addItem(AbstractItem item, int row, int column) {
		if (row >= rows)
			return false;
		else if (column >= columns)
			return addItem(item, row + 1, 0);
		else {
			final int index = row * columns + column;
			if (content[index] == null) {
				content[index] = item;
				notifyItemAdded(item);
				return true;
			} else
				return addItem(item, row, column + 1);
		}
	}
	
	/** Returns the removed item at the given position. **/
	public AbstractItem removeItem(int row, int column) {
		final int index = row * columns + column;
		final AbstractItem item = content[index];
		if (item != null) {
			notifyItemRemoved(item);
			items--;
			content[index] = null;
		}
		return item;
	}
	
	/** Returns true if the given item was removed from the inventory. **/
	public boolean removeItem(AbstractItem item) {
		for (int row = 0; row < rows; row++) {
			for (int column = 0; column < columns; column++) {
				final int index = row * columns + column;
				if (content[index] == item) {
					if (item != null) {
						notifyItemRemoved(item);
						items--;
						content[index] = null;
					}
					return true;
				}
			}
		}
		return false;
	}
	
	/** Removes all the items from the inventory. **/
	public void clearItems() {
		for (int row = 0; row < rows; row++) {
			for (int column = 0; column < columns; column++) {
				removeItem(row,column);
			}
		}
	}
	
	/** Copies the content of the given inventory into this inventory. **/
	public void copyFrom(Inventory inventory) {
		clearItems();
		for (int row = 0; row < rows; row++) {
			for (int column = 0; column < columns; column++) {
				AbstractItem item = inventory.getItem(row, column);
				if (item != null) {
					addItem(item.clone(),row,column);
				}
			}
		}
	}
	
	/** Returns the item at the given position in the inventory. If null is returned
	 * then no item is stored at the given position. The first item is stored at
	 * row 0 in column 0. **/
	public AbstractItem getItem(int row, int column) {
		return content[row * columns + column];
	}
	
	/** Swaps the position of two items in the inventory. **/
	public void swapItems(int row, int column, int newRow, int newColumn) {
		final int toIndex = newRow * columns + newColumn;
		final int fromIndex = row * columns + column;
		AbstractItem toItem = content[toIndex];
		notifyItemSwapped(toItem,content[fromIndex]);
		content[toIndex] = content[fromIndex];
		content[fromIndex] = toItem;
	}
	
	/** Copies the row and column of an abstract item in the inventory 
	 * to the point (x = row, y = column) and return true. If the item was
	 * not found then false is returned.*/
	public boolean getPosition(AbstractItem item, Point position) {
		for (int row = 0; row < rows; row++) {
			for (int column = 0; column < columns; column++) {
				final int index = row * columns + column;
				if (content[index] == item) {
					position.setLocation(row, column);
					return true;
				}
			}
		}
		return false;
	}
	
	/** Returns true and sets the provided point to the location
	 * of the successor to the given item. If the given item has
	 * no successor then false is returned. **/
	public boolean getSuccessor(AbstractItem item, Point position) {
		boolean successor = false;
		boolean foundPredecessor = false;

		for (int row = 0; row < rows; row++) {
			for (int column = 0; column < columns; column++) {
				
				AbstractItem i = getItem(row, column);
				if (i == item) {
					successor = true;
					
				} else if (i != null) {
					// There is an item stored at the current position
					if (!foundPredecessor) {
						position.setLocation(row, column);
						foundPredecessor = true;
						
					} else if (successor) {
						position.setLocation(row, column);
						return true;
					}
				}
					
			}
		}
		
		if (foundPredecessor)
			return true;
		else
			return false;	
	}
	
	/** Returns true and sets the provided point to the location
	 * of the predecessor to the given item. If the given item has
	 * no successor then false is returned. **/
	public boolean getPredecessor(AbstractItem item, Point position) {
		boolean foundSuccessor = false;
		boolean predecessor = false;

		for (int row = rows-1; row >= 0; row--) {
			for (int column = columns-1; column >= 0; column--) {
				
				AbstractItem i = getItem(row, column);
				if (i == item) {
					predecessor = true;
					
				} else if (i != null) {
					// There is an item stored at the current position
					if (!foundSuccessor) {
						position.setLocation(row, column);
						foundSuccessor = true;
						
					} else if (predecessor) {
						position.setLocation(row, column);
						return true;
					}
				}
					
			}
		}
		
		if (foundSuccessor)
			return true;
		else
			return false;	
	}
	
	/*============================================================
	Private Methods
	============================================================*/
	
	/** Notifies to all the listeners that the given item has been added
	 * to the inventory. **/
	private void notifyItemAdded(AbstractItem item) {
		for (InventoryListener listener : listeners.toArray(new InventoryListener[0]))
			listener.itemAdded(this, item);
	}
	
	/** Notifies to all the listeners that the given item has been removed
	 * from the inventory. **/
	private void notifyItemRemoved(AbstractItem item) {
		for (InventoryListener listener : listeners.toArray(new InventoryListener[0]))
			listener.itemRemoved(this, item);
	}
	
	/** Notifies to all the listeners that the inventory has been clear
	 * from all items. **/
	private void notifyItemSwapped(AbstractItem item1, AbstractItem item2) {
		for (InventoryListener listener : listeners.toArray(new InventoryListener[0]))
			listener.itemSwapped(this, item1, item2);
	}
	
	/** Notifies to all the listeners that the inventory 
	 * has been resized. **/
	private void notifyInventoryResized(Size size) {
		for (InventoryListener listener : listeners.toArray(new InventoryListener[0]))
			listener.inventoryResized(this, size);
	}
	
	/** Updates the number of rows and columns of the inventory and
	 * update the array containing all items in the inventory. **/
	private void updateInventory(Size size) {
		// Update the size of the inventory
		columns = columnsOf(size);
		rows = rowsOf(size);
				
		// Update the content of the inventory
		AbstractItem[] newContent = new AbstractItem[rows*columns];
		items = 0;
		if (content != null) {
			final int min = content.length < newContent.length ? 	content.length : 
																	newContent.length;
			for (int i = 0; i < min; i++) {
				if (content[i] != null)
					items++;
				newContent[i] = content[i];
			}
		}
		content = newContent;
	}

}
